﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace engine
{
    public class Terrain
    {
      

        public GraphicsDevice graphics;
        private BasicEffect ef;
        private short terrainWidth;
        private short terrainHeight;
        private VertexPositionNormalTexture[] vertices;
        private VertexDeclaration vertexDeclaration;
        private short[] indices;
        private float[,] heightData;

        private VertexBuffer vertexBuffer = null;
        private IndexBuffer indexBuffer = null;

        public Matrix Transform { get; set; }

        private Scene scene;

        public Terrain()
        {
            graphics = Engine.Instance.Game.GraphicsDevice;
        }

        public void Initialize(Scene scene)
        {
            this.scene = scene;
            ef = new BasicEffect(graphics, null);
            ef.TextureEnabled = true;

            Transform = Matrix.CreateScale(100);

        }

        public void LoadContent()
        {
            ef.Texture = Engine.Instance.Content.Load<Texture2D>("Textures\\heightmap");

            Texture2D heightMap = Engine.Instance.Content.Load<Texture2D>("Textures\\heightmap");

            LoadHeightData(heightMap);
            SetUpVertices();
            SetUpIndices();

            CalculateNormals();
        }
        //this method make that terrain will grow into the positive X direction (Right) and into the negative Z direction (Forward).
        public void SetUpVertices()
        {

            vertices = new VertexPositionNormalTexture[terrainWidth * terrainHeight];

            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainHeight; y++)
                {
                    vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);
                    vertices[x + y * terrainWidth].TextureCoordinate = new Vector2((float)x / (float)terrainWidth, (float)y / (float)terrainHeight);
                    vertices[x + y * terrainWidth].Normal = new Vector3(0, 0, 1);
                }
            }

            vertexDeclaration = new VertexDeclaration(graphics, VertexPositionNormalTexture.VertexElements);

            vertexBuffer = new VertexBuffer(graphics,
                vertices.Length * VertexPositionNormalTexture.SizeInBytes, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

        }
        //now we defining the indices which define the required triangles to connect the vertices 
        public void SetUpIndices()
        {
            indices = new short[(terrainWidth - 1) * (terrainHeight - 1) * 6];
            int counter = 0;
            for (short y = 0; y < terrainHeight - 1; y++)
            {
                for (short x = 0; x < terrainWidth - 1; x++)
                {
                    short lowerLeft = (short)(x + y * terrainWidth);
                    short lowerRight = (short)((x + 1) + y * terrainWidth);
                    short topLeft = (short)(x + (y + 1) * terrainWidth);
                    short topRight = (short)((x + 1) + (y + 1) * terrainWidth);

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }

            indexBuffer = new IndexBuffer(graphics, typeof(short),
                indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
        }

        //now we can calculate our normals, which will be done in the CalculateNormal method
        //we need to clear normals in each of our vertices
        private void CalculateNormals()
        {
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            //now we calculate normals for each the triangle's 3 vertices 
            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }
        //now we define method how pro
        public void LoadHeightData(Texture2D heightMap)
        {
            if (heightMap.Width * heightMap.Height * 6 > 65536)
            {
                return;
            }
            terrainWidth = (short)heightMap.Width;
            terrainHeight = (short)heightMap.Height;

            Color[] heightMapColors = new Color[terrainWidth * terrainHeight];
            heightMap.GetData(heightMapColors);

            heightData = new float[terrainWidth, terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainHeight; y++)
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R / 5.0f;
        }




        public void Draw(GameTime gameTime)
        {
            graphics.VertexDeclaration = vertexDeclaration;
            graphics.RenderState.CullMode = CullMode.None;
            //graphics.RenderState.FillMode = FillMode.WireFrame;

            graphics.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
            graphics.Indices = indexBuffer;

            ef.EnableDefaultLighting();
            ef.World = Transform;
            ef.View = scene.Camera.View;
            ef.Projection = scene.Camera.Projection;


            ef.Begin();

            foreach (EffectPass pass in ef.CurrentTechnique.Passes)
            {

                pass.Begin();
                //graphics.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
                graphics.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3);
                pass.End();
            }

            ef.End();


        }
    }
}
